//  PMXCrossover.java
//
//  Author:
//       Antonio J. Nebro <antonio@lcc.uma.es>
//       Juan J. Durillo <durillo@lcc.uma.es>
//
//  Copyright (c) 2011 Antonio J. Nebro, Juan J. Durillo
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
// 
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

package jmetal.operators.crossover;


import hidra.jmetal.core.*;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;

import jmetal.encodings.solutionType.BinaryRealSolutionType;
import jmetal.encodings.solutionType.BinarySolutionType;
import jmetal.encodings.solutionType.PermutationSolutionType;
import jmetal.encodings.variable.*;
import jmetal.util.Configuration;
import jmetal.util.JMException;
import jmetal.util.PseudoRandom;

/**
 * This class allows to apply a PMX crossover operator using two parent
 * solutions.
 * NOTE: the operator is applied to the first variable of the solutions, and 
 * the type of those variables must be VariableType_.Permutation.
 */
public class PMXCrossover extends Crossover {

	/**
	 * Valid solution types to apply this operator 
	 */
	private static List VALID_TYPES = Arrays.asList(PermutationSolutionType.class) ;

  private Double crossoverProbability_ = null;

	/**
	 * Constructor
	 */
	public PMXCrossover(HashMap<String, Object> parameters) {
		super(parameters) ; 

		if (parameters.get("probability") != null)
  		crossoverProbability_ = (Double) parameters.get("probability") ;  		
	} // PMXCrossover


	/**
	 * Constructor
	 */
	//public PMXCrossover(Properties properties) {
	//	this();
	//} // PMXCrossover


	/**
	 * Perform the crossover operation
	 * @param probability Crossover probability
	 * @param parent1 The first parent
	 * @param parent2 The second parent
	 * @return An array containig the two offsprings
	 * @throws JMException 
	 */
	public Solution[] doCrossover(double   probability, 
			Solution parent1, 
			Solution parent2) throws JMException {

		Solution [] offspring = new Solution[2];

		offspring[0] = new Solution(parent1);
		offspring[1] = new Solution(parent2);

		int permutationLength ;

		permutationLength = ((Permutation)parent1.getDecisionVariables()[0]).getLength() ;

		int parent1Vector[]    = ((Permutation)parent1.getDecisionVariables()[0]).vector_ ;
		int parent2Vector[]    = ((Permutation)parent2.getDecisionVariables()[0]).vector_ ;    
		int offspring1Vector[] = ((Permutation)offspring[0].getDecisionVariables()[0]).vector_ ;
		int offspring2Vector[] = ((Permutation)offspring[1].getDecisionVariables()[0]).vector_ ;

		if (PseudoRandom.randDouble() < probability) {
			int cuttingPoint1 ;
			int cuttingPoint2 ;

			//      STEP 1: Get two cutting points
			cuttingPoint1 = PseudoRandom.randInt(0,permutationLength-1) ;
			cuttingPoint2 = PseudoRandom.randInt(0,permutationLength-1) ;
			while (cuttingPoint2 == cuttingPoint1)	
				cuttingPoint2 = PseudoRandom.randInt(0,permutationLength-1) ;

			if (cuttingPoint1 > cuttingPoint2) {
				int swap ;
				swap          = cuttingPoint1 ;
				cuttingPoint1 = cuttingPoint2 ;
				cuttingPoint2 = swap          ;
			} // if
			//      STEP 2: Get the subchains to interchange
			int replacement1[] = new int[permutationLength] ;
			int replacement2[] = new int[permutationLength] ;
			for (int i = 0; i < permutationLength; i++)
				replacement1[i] = replacement2[i] = -1;

			//      STEP 3: Interchange   	
			for (int i = cuttingPoint1; i <= cuttingPoint2; i++) {
				offspring1Vector[i] = parent2Vector[i] ;
				offspring2Vector[i] = parent1Vector[i] ;

				replacement1[parent2Vector[i]] = parent1Vector[i] ;
				replacement2[parent1Vector[i]] = parent2Vector[i] ;
			} // for

			//      STEP 4: Repair offsprings
			for (int i = 0; i < permutationLength; i++) {
				if ((i >= cuttingPoint1) && (i <= cuttingPoint2)) 
					continue ; 

				int n1 = parent1Vector[i] ;
				int m1 = replacement1 [n1] ;

				int n2 = parent2Vector[i] ;
				int m2 = replacement2 [n2] ;

				while (m1 != -1) {
					n1 = m1 ;
					m1 = replacement1[m1] ;
				} // while
				while (m2 != -1) {
					n2 = m2 ;
					m2 = replacement2[m2] ;
				} // while
				offspring1Vector[i] = n1 ;
				offspring2Vector[i] = n2 ;
			} // for
		} // if

		return offspring ;                                                                                      
	} // doCrossover

	/**
	 * Executes the operation
	 * @param object An object containing an array of two solutions 
	 * @throws JMException 
	 */
	public Object execute(Object object) throws JMException {
		Solution [] parents = (Solution [])object;
		Double crossoverProbability = null ;

		if (!(VALID_TYPES.contains(parents[0].getType().getClass())  &&
				VALID_TYPES.contains(parents[1].getType().getClass())) ) {

			Configuration.logger_.severe("PMCCrossover.execute: the solutions " +
					"are not of the right type. The type should be 'Permutation', but " +
					parents[0].getType() + " and " + 
					parents[1].getType() + " are obtained");
		}

		//crossoverProbability = (Double)parameters_.get("probability");
		crossoverProbability = (Double)getParameter("probability");

		if (parents.length < 2)
		{
			Configuration.logger_.severe("PMXCrossover.execute: operator needs two " +
			"parents");
			Class cls = java.lang.String.class;
			String name = cls.getName(); 
			throw new JMException("Exception in " + name + ".execute()") ;      
		}

		Solution [] offspring = doCrossover(crossoverProbability.doubleValue(),
				parents[0],
				parents[1]);

		return offspring; 
	} // execute
} // PMXCrossover
